package org.duang.db.mongo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.bson.BsonDocument;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.duang.db.common.ExceOper;
import org.duang.db.common.Page;
import org.duang.db.common.Query;
import org.duang.db.common.Update;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.MongoClient;
import com.mongodb.client.FindIterable;
import com.mongodb.client.ListIndexesIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.result.UpdateResult;

public class Mongo extends MongoConnection {

	private final static Logger logger = LoggerFactory.getLogger(Mongo.class);
	
	private static Mongo mongo;

	public Mongo() {
   		mongodbConn = new MongoConnection();
	}
	
	private Mongo(Map<String, String> optionMap) {
   		mongodbConn = new MongoConnection();
   		try {
			mongodbConn.connect(optionMap);
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
		}
	}

	public static Mongo getInstance() {
		if (null == mongo) {
			mongo = new Mongo();
		}
		return mongo;
	}
	
	public static Mongo newInstance() {
		return new Mongo();
	}
	
	public MongoClient getClient() {
		return client;
	}
	
    /**
     * 取所有数据库名称
     * @return
     */
	public List<String> getDatabaseNames(){
		return client.getDatabaseNames();
	}
	
	/**
	 * 取得所有表名
	 * @return
	 */
	public Set<String> getCollectionNames(){
		return db.getCollectionNames();
	}


	public DB getDB() {
		return db;
	}

	public MongoCollection<Document> getCollection(String databaseName, String collectionName) {
		return client.getDatabase(databaseName).getCollection(collectionName);
	}
	
	public Page<DBObject> exec(ExceOper oper) {
		MongoCollection<Document> collection = getCollection(oper.getDatabaseName(), oper.getCollectionName());
		Query query = oper.getQuery();
		Update update = oper.getUpdate();
		if(null == update){
			return execQuery(collection, query);
		} else {
			return execUpdate(collection, query, update);
		}
	}

	@SuppressWarnings("unchecked")
	private Page<DBObject> execQuery(MongoCollection<Document> collection, Query query) {
		DBObject queryObj = query.getQueryObj();
		DBObject fields =  query.getDBFields();
		DBObject sorts = query.getDBOrder();
		Page<DBObject> page = query.getPage();
		int pageNo = page.getPageNo();
		int pageSize = page.getPageSize();
		Bson findBson = (null == queryObj) ? BsonDocument.parse("{}") : BsonDocument.parse(queryObj.toString());
		Bson fieldsBson = (null == fields) ? BsonDocument.parse("{}") :  BsonDocument.parse(fields.toString());
		Bson sortBson = (null == sorts) ? BsonDocument.parse("{}") : BsonDocument.parse(sorts.toString());
		logger.debug("find: " + findBson.toString());
		logger.debug("field: " + fieldsBson.toString());
		logger.debug("sort: " + sortBson.toString());
		FindIterable<Document> docs = collection.find(findBson).projection(fieldsBson).sort(sortBson).skip((pageNo-1)*pageSize).limit(pageSize);
		List<DBObject> result = new ArrayList<DBObject>();
		for(Iterator<Document> it = docs.iterator(); it.hasNext(); ){
			Document doc = it.next();
			if(null != doc){
				DBObject dbo = new BasicDBObject();
				for(Iterator<Entry<String, Object>> it2 = doc.entrySet().iterator();  it2.hasNext();){
					Entry<String, Object> entry = it2.next();
					dbo.put(entry.getKey(), entry.getValue());
				}
				result.add(dbo);
			}
		}
		page.setResult(result);
		if(page.isAutoCount()){
			page.setTotalCount(collection.count(findBson));
		}
		return page;
	}
	
	private Page<DBObject> execUpdate(MongoCollection<Document> collection, Query query, Update update) {
		DBObject queryObj = query.getQueryObj();
		DBObject updateObj = update.getUpdateObj();
		Bson findBson = (null == queryObj) ? BsonDocument.parse("{}") : BsonDocument.parse(queryObj.toString());
		Bson updateBson = (null == updateObj) ? BsonDocument.parse("{}") : BsonDocument.parse(updateObj.toString());
		logger.debug("find: " + findBson.toString());
		logger.debug("update: " + updateBson.toString());
		UpdateResult result = null;
		DBObject doc = new BasicDBObject();
		long modifCount = 0l;
		String error = "";
		try{
			result = collection.updateMany(findBson, updateBson);
			logger.debug(result.toString());
			modifCount = result.isModifiedCountAvailable() ?  result.getModifiedCount() : result.getMatchedCount();
		} catch(Exception ex) {
			logger.debug(ex.getMessage(), ex);
			error = ex.getMessage();
		}
		doc.put("updteExisting", (modifCount > 0));
		doc.put("n", modifCount);
		doc.put("ok", modifCount);
		doc.put("err", error);
		Page<DBObject> page = new Page<DBObject>(0,1);
		List<DBObject> resultList = new ArrayList<DBObject>(1);
		resultList.add(doc);
		page.setResult(resultList);
		return page;
	}
	
	
	public List<CollectionDoc> getAllCollection(String databaseName){
//		client.getDB(databaseName).getCollectionNames();
		MongoDatabase db = client.getDatabase(databaseName);
		if(null == db) return null;
		MongoIterable<String> iterable =  db.listCollectionNames();
		ArrayList<String> list = new ArrayList<String>();
		for(String name : iterable){
			list.add(name);
		}
		String[] arrayToSort = list.toArray(new String[list.size()]);
	    Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		if(null == iterable || null == arrayToSort) return null;
		List<CollectionDoc> collectionList = new ArrayList<CollectionDoc>();
		for(String name : arrayToSort){
			CollectionDoc doc = new CollectionDoc();
			doc.setCollectionName(name);
			ListIndexesIterable<Document> indexs = db.getCollection(name).listIndexes();
			Set<String> indexSet = new java.util.LinkedHashSet<String>();
			for(Iterator<Document> it = indexs.iterator(); it.hasNext();){
				Document index = it.next();
				for(Iterator<Entry<String,Object>> it2 = index.entrySet().iterator(); it2.hasNext();){
					Entry<String,Object> entry = it2.next();
					if("name".equals(entry.getKey())){						
						indexSet.add(entry.getValue().toString());
					}
				}
			}
			doc.setIndexs(indexSet);
			collectionList.add(doc);
		}
		return collectionList;
	}
}
